<?php

	/**
	 * PHP Sourcecode Statistics
	 *
	 * License: GPL v2
	 * Author: Niels Jäckel
	 * Website: http://silice.de/
	 */


	chdir('..'. DIRECTORY_SEPARATOR);

	// the starting package (subdirectory with dot-notation)
	define('ROOT_PACKAGE', 'org.tekuna');

	// comment lines in comparison to source code lines --> source code documentation
	define('COMMENT_RATIO_MIN', 50); // 50%

	// blank lines in comparison to all lines --> code structure
	define('BLANK_LINE_RATIO_MIN', 30); // 30%

	// maximum lines per file --> code overview
	define('LINES_PER_FILE_MAX', 300); // 300 lines

	// php source file pattern
	define('PHP_FILENAME_PATTERN', '~^.+\.(?:php|inc)$~i');


	echo '<?xml version="1.0" encoding="UTF-8" ?>';

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
	<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
	<head>
		<title>Tekuna Sourcecode Statistics</title>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<meta name="language" content="en" />

		<style type="text/css">

			* {

				margin:0;
				padding:0;
				font-size:1em;
				font-weight:normal;
			}

			body {

				font-size:62.5%;
				font-family:Verdana,Sans-serif;

				padding:2em;
			}

			h1 {

				font-size:2em;
				margin:0 0 2em 0;
				border-bottom:0.05em solid black;
			}

			p.error {

				font-size:1.2em;
				color:red;
				border:0.08333em solid red;
				padding:1em;
				margin:0 0 1em 0;
			}

			table.stats {

				border-spacing:0;

				border-top:0.1em solid #888;
				border-left:0.1em solid #888;

				clear:left;

				margin:2em 0;
			}

			table.stats tbody tr:hover,
			table.stats tr th {

				background-color:#CCC;
			}

			table.stats tr td,
			table.stats tr th {

				font-size:1.2em;
				padding:0.2em;
				border-right:0.0833em solid #888;
				border-bottom:0.0833em solid #888;
			}

			table.stats tr th {

				font-weight:bold;
				vertical-align:middle;
			}

			table.stats tr.package td.first {

				font-weight:bold;
			}

			table.stats tr.package {

				background-color:#DDD;
			}

			table.stats tr th span,
			table.stats td.null {

				color:#AAA;
			}

			table.stats td.bad {

				color:red;
				font-weight:bold;
			}

			dl {

				clear:left;
			}

			dl * {

				font-size:1.4em;

				padding:.4em 0 0 0;
			}

			dt {

				float:left;
				width:18em;
				font-weight:bold;
			}

			dd {

				float:left;
			}

			p {

				width:30em;
				clear:left;
				text-align:right;
				margin:3em 0;
			}

		</style>

	</head>
	<body>

		<h1>Tekuna Sourcecode Statistics</h1>

<?php

	// init statistics array
	$arrStatistics = array();

	// increase the time limit for the search
	@set_time_limit(3600);

	// collect statistics recursively into the empty array
	getPackageStatistics(ROOT_PACKAGE, $arrStatistics);

	// sort the result
	ksort($arrStatistics);




	function getPackageStatistics($sPackage, & $arrStatistics) {

		$arrPackageStatistics = array('files' => 0,
		                              'packageFiles' => 0,
		                              'loc' => 0,
		                              'bloc' => 0,
		                              'cloc' => 0,
		                              'sloc' => 0,
		                              'fileStatistics' => array());

		$sPackagePath = str_replace('.', DIRECTORY_SEPARATOR, $sPackage);

		// demask dot-directories
		$sPackagePath = str_replace('\\'.DIRECTORY_SEPARATOR, '.', $sPackagePath);
		$objDir = @dir($sPackagePath);

		if ($objDir === FALSE) {

			// print error message
			echo "<p class=\"error\">Error reading $sPackagePath... </p>";

			// return empty statistics
			return $arrPackageStatistics;
		}

		while ($sFile = $objDir -> read()) {

			// skip dot-files
			if ($sFile{0} == '.') {

				continue;
			}

			$sFullPath = $sPackagePath . DIRECTORY_SEPARATOR . $sFile;

			// recurse directories
			if (is_dir($sFullPath)) {

				// mask dots in the directory name
				$sFile = str_replace('.', '\.', $sFile);

				// build subpackage
				$sSubpackage = "$sPackage.$sFile";

				// get subpackage statistics
				$arrSubpackageStatistics = getPackageStatistics($sSubpackage, $arrStatistics);

				// add subpackage statistics to current package statistics
				foreach ($arrSubpackageStatistics as $sKey => $iValue) {

					if ($sKey != 'fileStatistics' &&
					    $sKey != 'packageFiles') {

						$arrPackageStatistics[$sKey] += $iValue;
					}
				}
			}
			else {

				// analyze only PHP files
				if (preg_match(PHP_FILENAME_PATTERN, $sFile)) {

					// increase files count
					$arrPackageStatistics['files']++;
					$arrPackageStatistics['packageFiles']++;

					// get the file statistics
					$arrFileStatistics = analyzeFile($sFullPath);

					// add file statistics to current package statistics
					foreach ($arrFileStatistics as $sKey => $iValue) {

						$arrPackageStatistics[$sKey] += $iValue;
					}

					// store complete statistics, too
					$arrPackageStatistics['fileStatistics'][$sFullPath] = $arrFileStatistics;
				}
			}
		}

		// store stats of this package
		$arrStatistics[$sPackage] = $arrPackageStatistics;

		return $arrPackageStatistics;
	}






	function analyzeFile($sPath) {

		$arrValues = array('loc' => 0,   // lines of code ... hardly every line
		                   'bloc' => 0,  // blank lines
		                   'cloc' => 0,  // comment lines
		                   'sloc' => 0); // real code lines

		$arrLines = file($sPath);
		$bInsideComment = FALSE;

		foreach ($arrLines as $sLine) {

			// remove all leading and trailing whitespaces
			$sLine = trim($sLine);

			// do not count first php line
			if (substr($sLine, 0, 5) == '<?php' ||
			    substr($sLine, 0, 2) == '?>') {

				continue;
			}

			// every line is a code line
			$arrValues['loc']++;

			// blank lines
			if ($sLine == '') {

				// increase blank line count
				$arrValues['bloc']++;

				continue;
			}

			// one line comments
			if ($sLine{0} == '#' ||
			    ($sLine{0} == '/' && $sLine{1} == '/')) {

				// increase comment line count
				$arrValues['cloc']++;

				continue;
			}

			if (strpos($sLine, '/*') !== FALSE) {

				$bInsideComment = TRUE;
			}

			if ($bInsideComment && strpos($sLine, '*/') !== FALSE) {

				$bInsideComment = FALSE;
			}

			// multi line comments
			if ($bInsideComment) {

				// increase comment line count
				$arrValues['cloc']++;

				continue;
			}

			if (! $bInsideComment) {

				// increase comment line count
				$arrValues['sloc']++;
			}
		}

		return $arrValues;
	}



?>

<dl>
	<dt>Source Lines Of Code (SLOC):</dt>
	<dd><?php echo $arrStatistics[ROOT_PACKAGE]['sloc']; ?></dd>
</dl>

<dl>
	<dt>All Lines Of Code (LOC):</dt>
	<dd><?php echo $arrStatistics[ROOT_PACKAGE]['loc']; ?></dd>
</dl>

<dl>
	<dt>Count Of Files:</dt>
	<dd><?php echo $arrStatistics[ROOT_PACKAGE]['files']; ?></dd>
</dl>

<p>
(all including tests)
</p>

<table class="stats">

	<?php

		$iLinesPerFileMax = LINES_PER_FILE_MAX;
		$fCommentRatioMin = COMMENT_RATIO_MIN;
		$fBlankLineRatioMin = BLANK_LINE_RATIO_MIN;

		$sTableHeader = <<<EOF
	<thead>
		<tr>
			<th>Package</th>
			<th>Package files</th>
			<th>Files</th>
			<th>LOC</th>
			<th>BLOC</th>
			<th>CLOC</th>
			<th>SLOC</th>
			<th title="maximum: $iLinesPerFileMax lines">LPF<br /><span>(LOC / Files)</span></th>
			<th>Code ratio<br /><span>(SLOC / LOC)</span></th>
			<th title="minimum: $fCommentRatioMin&thinsp;%">Comment ratio<br /><span>(CLOC / SLOC)</span></th>
			<th title="minimum: $fBlankLineRatioMin&thinsp;%">Blank line ratio<br /><span>(BLOC / SLOC)</span></th>
		</tr>
	</thead>
EOF;

		// show header and begin the table body
		echo $sTableHeader;
		echo '<tbody>';

		$iLinesCounter = 0;
		foreach ($arrStatistics as $sPackage => $arrStats) {

			// insert new thead area if 40 lines are gone
			if ($iLinesCounter > 40) {

				// reset counter
				$iLinesCounter = 0;

				// end table body, fill in additional table headers and begin the new table body
				echo '</tbody>';
				echo $sTableHeader;
				echo '<tbody>';
			}

			// the package lines
			getStatisticsTableLine($sPackage, $arrStats);

			// the file lines
			foreach ($arrStats['fileStatistics'] as $sPath => $arrFileStats) {

				getStatisticsTableLine($sPath, $arrFileStats, FALSE);
				$iLinesCounter++;
			}
		}

		// end the last table body
		echo '</tbody>';


		function getStatisticsTableLine($sPackage, $arrStats, $bIsPackage = TRUE) {

			// get values
			if ($bIsPackage) {

				$iPackageFiles = $arrStats['packageFiles'];
				$iFiles = $arrStats['files'];
				$iLPF = ($iFiles == 0) ? 0 : round($arrStats['loc'] / $iFiles);
			}
			else {

				$iLPF = $arrStats['loc'];
			}

			$iLOC = $arrStats['loc'];
			$iBLOC = $arrStats['bloc'];
			$iCLOC = $arrStats['cloc'];
			$iSLOC = $arrStats['sloc'];
			$fCodeRatio = ($arrStats['loc'] == 0) ? 0 : round($arrStats['sloc'] / $arrStats['loc'] * 100, 1);
			$fCommentRatio = ($arrStats['sloc'] == 0) ? 0 : round($arrStats['cloc'] / $arrStats['sloc'] * 100, 1);
			$fBlankLineRatio = ($arrStats['sloc'] == 0) ? 0 : round($arrStats['bloc'] / $arrStats['sloc'] * 100, 1);

			// init special attribute classes
			if ($bIsPackage) {

				$sPackageFilesClasses = '';
				$sFilesClasses = '';
			}

			$sLOCClasses = '';
			$sBLOCClasses = '';
			$sCLOCClasses = '';
			$sSLOCClasses = '';
			$sLPFClasses = '';
			$sCodeRatioClasses = '';
			$sCommentRatioClasses = '';
			$sBlankLineClasses = '';

			// fill special attribute classes
			if ($bIsPackage) {

				if ($iPackageFiles == 0) { $sPackageFilesClasses .= 'null '; }
				if ($iFiles == 0) { $sFilesClasses .= 'null '; }
			}

			if ($iLOC == 0) { $sLOCClasses .= 'null '; }
			if ($iBLOC == 0) { $sBLOCClasses .= 'null '; }
			if ($iCLOC == 0) { $sCLOCClasses .= 'null '; }
			if ($iSLOC == 0) { $sSLOCClasses .= 'null '; }
			if ($iLPF == 0) { $sLPFClasses .= 'null '; }
			if ($fCodeRatio == 0) { $sCodeRatioClasses .= 'null '; }
			if ($fCommentRatio == 0) { $sCommentRatioClasses .= 'null '; }
			if ($fBlankLineRatio == 0) { $sBlankLineClasses .= 'null '; }

			// mark bad code metrics
			if ($iLPF > LINES_PER_FILE_MAX && $iLOC > 0) { $sLPFClasses .= 'bad '; }
			if ($fCommentRatio < COMMENT_RATIO_MIN && $iLOC > 0) { $sCommentRatioClasses .= 'bad '; }
			if ($fBlankLineRatio < BLANK_LINE_RATIO_MIN && $iLOC > 0) { $sBlankLineClasses .= 'bad '; }


			if ($bIsPackage) {

				echo "<tr class=\"package\">";
				echo "<td class=\"first\">$sPackage</td>";
				echo "<td class=\"$sPackageFilesClasses\">$iPackageFiles</td>";
				echo "<td class=\"$sFilesClasses\">$iFiles</td>";
			}
			else {

				echo "<tr class=\"file\">";
				echo "<td colspan=\"3\">&emsp;&emsp;$sPackage</td>";
			}

			echo "<td class=\"$sLOCClasses\">$iLOC</td>";
			echo "<td class=\"$sBLOCClasses\">$iBLOC</td>";
			echo "<td class=\"$sCLOCClasses\">$iCLOC</td>";
			echo "<td class=\"$sSLOCClasses\">$iSLOC</td>";
			echo "<td class=\"$sLPFClasses\">$iLPF</td>";
			echo "<td class=\"$sCodeRatioClasses\">$fCodeRatio&thinsp;%</td>";
			echo "<td class=\"$sCommentRatioClasses\">$fCommentRatio&thinsp;%</td>";
			echo "<td class=\"$sBlankLineClasses\">$fBlankLineRatio&thinsp;%</td>";
			echo "</tr>";
		}

	?>
</table>
</body>
</html>
